﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema;
using EnvDTE;
using TFDP.VsPackage.Support;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio;
using TFDP.Common;
using System.Diagnostics;
using TFDP.Common.Configuration;
using TFDP.VsPackage.Services;
using Microsoft.VisualStudio.Shell;
using TFDP.VsPackage.Forms;
using System.Runtime.InteropServices;
using TFDP.VisualStudioInterop;

namespace TFDP.VsPackage.Extenders
{
    [DatabaseSchemaProviderCompatibility(typeof(DatabaseSchemaProvider))]
    public sealed class ProjectExtender : IDatabaseProjectExtenderContributor<IDatabaseNode>, IDatabaseProjectFeature
    {
        #region Members
        private IDatabaseProjectNode project;
        private ProjectManager manager;
        private RegenerateObjectService regenerateService;
        private ProjectNotifierService notifierService;
        private IVsPackage package;
        #endregion

        #region Constants
        public const string ExtenderCATID = "{5C26E30F-BFFA-4a2c-899B-B53A65379870}";
        #endregion

        #region Properties
        /// <summary>
        /// The current project
        /// </summary>
        private IDatabaseProjectNode Project
        {
            get { return project; }
            set { project = value; }
        } 
        #endregion

        #region IDatabaseProjectFeature Members
        public void Initialize(IDatabaseProjectNode projectNode)
        {
            Project = projectNode;

            if (Logger.Instance == null)
                Logger.CreateInstance(System.Diagnostics.TraceLevel.Warning);

            Logger.Instance.NewMessage += new EventHandler<LoggerEventArgs>(Instance_NewMessage);

            // Create the ProjectManager
            this.manager = ProjectManager.FromProject(projectNode);
            this.manager.ClearGenerationErrors += new EventHandler<ClearGenerationErrorsEventArgs>(manager_ClearGenerationErrors);
            this.manager.RequestFileEdit += new EventHandler<RequestFileEditEventArgs>(manager_RequestFileEdit);
            this.manager.QueueChanged += new EventHandler<QueueChangedEventArgs>(manager_QueueChanged);
            Project.ProjectClosed += new EventHandler<Microsoft.VisualStudio.Data.Schema.Package.Project.ProjectClosedEventArgs>(Project_ProjectClosed);

            regenerateService = new RegenerateObjectService();
            regenerateService.RegenerateObject += new EventHandler<RegenerateEventArgs>(service_RegenerateObject);
            Project.AddService(regenerateService);

            // Try to obtain the package
            // because we need the QueueNotifierService
            this.ObtainPackage();
            if (package != null)
            {
                this.notifierService = ((IServiceProvider)package).GetService(typeof(ProjectNotifierService)) as ProjectNotifierService;
                if (this.notifierService == null)
                    Trace.WriteLine("ProjectExtender: Unable to obtain QueueNotifierService");

                if(manager.Processor != null)
                    this.notifierService.RegisterProcessor(manager.Processor);
            }
            else
            {
                Trace.WriteLine("ProjectExtender: Unable to obtain IVsPackage");
            }

            this.manager.FileChangeService = ((IFileChangeService)((IServiceProvider)package).GetService(typeof(IFileChangeService)));
        }
        #endregion

        #region IDatabaseProjectExtenderContributor<IDatabaseNode> Members

        public bool CanExtend(string extenderName, IDatabaseNode extendee)
        {
            return (String.CompareOrdinal(extenderName, ExtenderCATID) == 0 && extendee is IDatabaseFileNode);
        }

        public bool GetExtender(string extenderName, IDatabaseNode extendee, IExtenderSite extenderSite, int cookie, out DatabaseProjectExtenderBase extender)
        {
            extender = null;

            // If we can extend the current extendee create a new Extender instance
            if (CanExtend(extenderName, extendee))
            {
                extender = new ExtendedType(extendee, extenderSite, cookie, manager.PropertyExtenderType);
                return true;
            }

            return false;
        }

        public string[] GetExtenderNames(IDatabaseNode extendee)
        {
            
            if (extendee is IDatabaseFileNode)
            {
                return new string[] { ExtenderCATID };
            }

            return null;
        }

        #endregion

        #region Database Project Eventhandlers
        private void Project_ProjectClosed(object sender, Microsoft.VisualStudio.Data.Schema.Package.Project.ProjectClosedEventArgs e)
        {
            if (this.notifierService != null)
                this.notifierService.NotifyProjectClosed(this.manager.Processor);

            if (this.manager != null)
            {
                this.manager.ClearGenerationErrors -= new EventHandler<ClearGenerationErrorsEventArgs>(manager_ClearGenerationErrors);
                this.manager.RequestFileEdit -= new EventHandler<RequestFileEditEventArgs>(manager_RequestFileEdit);
                
                this.manager.Dispose();

                this.manager = null;
            }
        }

        private void service_RegenerateObject(object sender, RegenerateEventArgs e)
        {
            this.manager.Enqueue(e.Node);
        }
        #endregion

        #region ProjectManager Eventhandlers
        private void manager_RequestFileEdit(object sender, RequestFileEditEventArgs e)
        {
            IVsQueryEditQuerySave2 qeqsService = null;
            try
            {
                EnvDTE.Project targetProject = VsInteropUtilities.GetEnvDTEProject(e.ProjectHierarchy);

                IServiceProvider serviceProvider = new ServiceProvider(targetProject.DTE as
                    Microsoft.VisualStudio.OLE.Interop.IServiceProvider);

                qeqsService = (IVsQueryEditQuerySave2)serviceProvider.GetService(typeof(SVsQueryEditQuerySave));
            }
            catch (Exception ex)
            {
                Logger.LogExtended(TraceLevel.Error, ex.Message, null, 0, 0);
            }

            if (qeqsService == null)
            {
                e.Result.Success = false;
                e.Result.Message = "Unable to obtain SVsQueryEditQuerySave service";
            }
            else
            {
                e.Result = qeqsService.CanEdit(e.Path);
            }
        }

        private void manager_QueueChanged(object sender, QueueChangedEventArgs e)
        {
            if (notifierService == null)
            {
                // Try to obtain the service
                this.notifierService = ((IServiceProvider)package).GetService(typeof(ProjectNotifierService)) as ProjectNotifierService;
            }

            if (notifierService != null)
                notifierService.NotifyQueueChanged(e.Processor);
        }

        private void manager_ClearGenerationErrors(object sender, ClearGenerationErrorsEventArgs e)
        {
            if (!String.IsNullOrEmpty(e.File))
            {
                this.project.ErrorManager.Remove(e.File, "TFDP");
            }
            else
            {
                this.Project.ErrorManager.ClearCategory("TFDP");
            }
        }
        #endregion

        #region Logger Eventhandlers
        private void Instance_NewMessage(object sender, LoggerEventArgs e)
        {
            if(this.Project.ErrorManager != null)
                this.Project.ErrorManager.Add(FromLoggerMessage(e), "TFDP");
        }

        private DataSchemaError FromLoggerMessage(LoggerEventArgs e)
        {
            ErrorSeverity severity = ErrorSeverity.Unknown;
            switch (e.Level)
            {
                case TraceLevel.Error:
                    severity = ErrorSeverity.Error;
                    break;
                case TraceLevel.Warning:
                    severity = ErrorSeverity.Warning;
                    break;
                case TraceLevel.Info:
                    severity = ErrorSeverity.Message;
                    break;
                default:
                    break;
            }

            DataSchemaError error = new DataSchemaError(e.Message, severity);

            if (e.ExtraInfo != null)
            {
                if (e.ExtraInfo.ContainsKey(LoggerEventArgs.Document))
                    error.Document = e.ExtraInfo[LoggerEventArgs.Document];

                if (e.ExtraInfo.ContainsKey(LoggerEventArgs.Line))
                {
                    int line = 1;
                    Int32.TryParse(e.ExtraInfo[LoggerEventArgs.Line], out line);
                    error.Line = line;
                }

                if (e.ExtraInfo.ContainsKey(LoggerEventArgs.Column))
                {
                    int column = 1;
                    Int32.TryParse(e.ExtraInfo[LoggerEventArgs.Column], out column);
                    error.Column = column;
                }
            }

            return error;
        }
        #endregion

        #region Support Methods
        private void ObtainPackage()
        {
            IVsShell shell = Package.GetGlobalService(typeof(SVsShell)) as IVsShell;
            Guid packageGuid = new Guid("{" + GuidList.guidTFDP_PackagePkgString + "}");
            IVsPackage package = null;
            shell.IsPackageLoaded(ref packageGuid, out package);

            this.package = package;
        }
        #endregion
    }
}
